#include "PageProcessor.h"
#include "DirectoryScanner.h"
#include "KeywordProcessor.h"

#include <set>
#include <iostream>
#include <tinyxml2.h>
#include <string>
#include <iomanip> // 设置浮点数格式，控制小数位数
#include <vector>
#include <regex>
#include <map>

using std::cerr;
using std::cout;
using std::endl;
using std::map;
using std::pair;
using std::set;
using std::string;
using std::vector;

PageProcessor::PageProcessor()
{
    std::vector<string> files = DirectoryScanner::scan("./corpus/stopwords");

    for (const auto &file : files)
    {
        std::ifstream ifs(file);
        string word;
        while (ifs >> word)
        {
            m_stopWords.insert(word);
        }
    }
}

namespace
{
    // 用匿名空间存放辅助函数-只对当前cpp文件有效
    bool hash_exists(std::vector<uint64_t> &hashcodes, uint64_t curr_h)
    {
        // 当前哈希，对比哈希库，查看有无小于海明距离文档
        using namespace simhash;

        for (const auto &h1 : hashcodes)
        {
            if (simhash::Simhasher::isEqual(h1, curr_h))
            {
                // 当前文档是重复文档
                return true;
            }
        }
        return false;
    }

    // 判断一个 Unicode 码点是否为中文字符
    // 涵盖基本汉字及扩展 A 到 H、兼容汉字和表意文字补充区
    bool chinese_char(uint32_t codepoint)
    {
        return (codepoint >= 0x4E00 && codepoint <= 0x9FFF) ||   // 基本汉字
               (codepoint >= 0x3400 && codepoint <= 0x4DBF) ||   // 扩展A
               (codepoint >= 0x20000 && codepoint <= 0x2A6DF) || // 扩展B
               (codepoint >= 0x2A700 && codepoint <= 0x2B73F) || // 扩展C
               (codepoint >= 0x2B740 && codepoint <= 0x2B81F) || // 扩展D
               (codepoint >= 0x2B820 && codepoint <= 0x2CEAF) || // 扩展E
               (codepoint >= 0x2CEB0 && codepoint <= 0x2EBEF) || // 扩展F
               (codepoint >= 0x30000 && codepoint <= 0x3134F) || // 扩展G
               (codepoint >= 0x31350 && codepoint <= 0x323AF) || // 扩展H
               (codepoint >= 0xF900 && codepoint <= 0xFAFF) ||   // 兼容汉字
               (codepoint >= 0x2F800 && codepoint <= 0x2FA1F);   // 表意文字补充区
    }

    // 判断一个字符串是否全为中文字符
    // 使用 utf8::iterator 遍历 UTF-8 编码的字符
    bool chinese_word(const std::string &word)
    {
        auto it = utf8::iterator{word.begin(), word.begin(), word.end()};
        auto end = utf8::iterator{word.end(), word.begin(), word.end()};
        for (; it != end; ++it)
        {
            if (!chinese_char(*it))
            {
                return false;
            }
        }
        return true;
    }
}

void PageProcessor::showProgressBar(const std::string &toolname, size_t current, size_t total, int width = 50)
{

    if (total == 0)
        return;

    int progress = static_cast<int>(current * width / total);
    cout << toolname << ":";
    cout << "[";
    cout << string(progress, '=') << ">";
    cout << string(width - progress, '-'); // 填充未完成部分
    cout << "]";
    // 百分比显示
    cout << std::fixed << std::setprecision(1) << (current * 100.0 / total) << "%\r";
    cout.flush(); // 确保进度条实时显示
}

void PageProcessor::process(const std::string &dir)
{
    extract_documents(dir);
    deduplicate_documents();
    build_pages_and_offsets("./pages.dat", "./offsets.dat");
    build_inverted_index("./inverted_index.dat");
}

// 提取xml文件
void PageProcessor::extract_documents(const std::string &dir)
{
    using namespace tinyxml2;

    auto files = DirectoryScanner::scan(dir);

    // 创建全局ID，从1开始，标注文件ID
    long uuid = 1;
    XMLDocument doc;
    for (const auto &file : files)
    {
        Document m_doc; // 定义数据结构，用于存储xml文件
        m_doc.id = uuid++;

        // showProgressBar("extract_documents", uuid, files.size());

        // 检查是否成功加载文件
        doc.LoadFile(file.c_str());
        if (doc.ErrorID())
        {
            cerr << "open file " << file << " error: " << doc.ErrorID() << endl;
            return;
        }

        // 查找XML中的<item>节点
        XMLElement *itemNode = doc.FirstChildElement("rss")->FirstChildElement("channel")->FirstChildElement("item");
        if (itemNode == nullptr)
        {
            return;
        }

        // 遍历<item>节点
        do
        {
            XMLElement *titleNode = itemNode->FirstChildElement("title");
            XMLElement *linkNode = itemNode->FirstChildElement("link");
            XMLElement *descriptionNode = itemNode->FirstChildElement("description");
            XMLElement *contentNode = itemNode->FirstChildElement("content:encoded");

            if (titleNode)
            {
                m_doc.title = titleNode->GetText();
            }
            if (linkNode)
            {
                m_doc.link = linkNode->GetText();
            }

            if (contentNode)
            {
                m_doc.content = contentNode->GetText();
            }
            else if (descriptionNode)
            {
                m_doc.content = descriptionNode->GetText();
            }

            // 清理 HTML 标签（使用正则表达式移除所有 <...> 标签）
            if (!m_doc.content.empty())
            {
                std::regex reg("<[^>]+>");                                  // 匹配所有 HTML 标签（如 <div>, <p> 等）
                m_doc.content = std::regex_replace(m_doc.content, reg, ""); // 替换为空字符串
            }

            // 构建格式化的文档字符串（<doc>...</doc>）
            string page;
            page.append("<doc>\n<docid>") // 开始 <doc> 标签
                .append(std::to_string(m_doc.id))
                .append("</docid>\n<docurl>") // 添加文档 ID
                .append(m_doc.link)
                .append("</docurl>\n<doctitle>") // 添加 URL
                .append(m_doc.title)
                .append("</doctitle>\n<doccontent>") // 添加标题
                .append(m_doc.content)
                .append("</doccontent>\n</doc>\n"); // 添加内容

            // 将构建好的文档字符串存入
            m_documents.push_back(std::move(m_doc)); // 使用 std::move 优化性能

        } while ((itemNode = itemNode->NextSiblingElement()) != nullptr);
    }
}

void PageProcessor::deduplicate_documents()
{
    std::vector<Document> uniq;      // 去重后的文档
    std::vector<uint64_t> hashcodes; // 存放计算的所有文档的哈希值

    for (size_t i = 0; i < m_documents.size(); i++)
    {
        uint64_t hash1;
        m_hasher.make(m_documents[i].content, SIMHASH_TOPN, hash1);
        if (!hash_exists(hashcodes, hash1))
        {
            // 如果当前文档不重复，则存入哈希值和document值
            hashcodes.push_back(hash1);
            uniq.push_back(m_documents[i]);
        }
        showProgressBar("deduplicate_documents", i, m_documents.size());
    }
    cout << endl;
    // 给m_documents赋值,移动语义方式
    m_documents = std::move(uniq);
}

void PageProcessor::build_pages_and_offsets(const std::string &pages, const std::string &offsets)
{
    using namespace std;
    ofstream out1{pages};
    ofstream out2{offsets};

    for (const auto &doc : m_documents)
    {
        auto start = out1.tellp(); // 查找out1当前位置
        out1 << "<doc>\n"
             << "<id>" << doc.id << "</id>\n"
             << "<link>" << doc.link << "</link>\n"
             << "<title>" << doc.title << "</title>\n"
             << "<content>" << doc.content << "</content>\n"
             << "</doc>\n";
        auto end = out1.tellp();

        out2 << doc.id << " " << start << " " << end - start << "\n";
    }
}

// 倒排索引库-根据词语查找文档id和权重
// TF：词语出现频率
// DF：出现过该词语的文档个数
// IDF：逆文档频率：log(总文档数/该词语出现的文档数)
void PageProcessor::build_inverted_index(const std::string &filename)
{
    int N = m_documents.size();

    // 正排索引：存储每个文档的<关键字，出现次数>映射
    map<int, map<string, int>> index;
    // 文档频率：存储每个关键字的DF值
    map<string, int> docFreq;

    // 遍历文档，构建正排索引
    for (const auto &doc : m_documents)
    {
        // 分词处理
        vector<string> words;
        m_tokenizer.Cut(doc.content, words);

        // 清洗数据，移除非中文词和停用词
        words.erase(
            remove_if(words.begin(), words.end(), [this](const string &word)
                      {
                // 非中文词或是停用词，则erase
                return !chinese_word(word) || (this->m_stopWords.find(word)!=this->m_stopWords.end()); }),
            words.end());

        // 统计每个词在当前文档中的出现次数
        map<string, int> frequencies;
        for (const auto &word : words)
        {
            frequencies[word]++;
        }

        // 更新每个关键字的文档频率DF，即出现在多少文档中
        set<string> wordSet{words.begin(), words.end()};
        for (const auto &word : wordSet)
        {
            docFreq[word]++;
        }

        // 将频率映射到正排索引
        index[doc.id] = move(frequencies);
    }

    // 计算倒排索引并应用TF-IDF权重
    for (const auto &[id, m] : index)
    {
        vector<pair<string, double>> weights;
        for (const auto &[word, TF] : m)
        {
            double DF = docFreq[word];
            double IDF = log2(N / DF);
            double w = TF * IDF;
            weights.emplace_back(make_pair(word, w));
        }

        // 归一化权重，计算向量模
        double square = 0.0;
        for (const auto &[_, w] : weights)
        {
            square += w * w; // 累加权重的平方
        }
        double sqRoot = sqrt(square); // 向量的模

        // 归一化每个权重
        for (const auto &[word, w] : weights)
        {
            double w1 = w / sqRoot;         // 归一化后的权重
            m_invertedIndex[word][id] = w1; // 存储到倒排索引
        }
    }

    // 将倒排索引保存到指定文件
    std::ofstream out{filename}; // 打开输出文件
    for (const auto &[word, m] : m_invertedIndex)
    {
        out << word << " "; // 输出关键字
        for (const auto &[id, weight] : m)
        {
            out << id << " " << weight << " "; // 输出<文档ID, 权重>
        }
        out << "\n"; // 换行
    }
}